package com.xiaopo.flying.puzzle.slant;

import com.xiaopo.flying.puzzle.Line;
import com.xiaopo.flying.puzzle.ext.PointF;
import ohos.utils.Pair;

import java.util.ArrayList;
import java.util.List;

/**
 * @author wupanjie
 */
class SlantUtils {
    private static final PointF A = new PointF();
    private static final PointF B = new PointF();
    private static final PointF C = new PointF();
    private static final PointF D = new PointF();
    private static final PointF AB = new PointF();
    private static final PointF AM = new PointF();
    private static final PointF BC = new PointF();
    private static final PointF BM = new PointF();
    private static final PointF CD = new PointF();
    private static final PointF CM = new PointF();
    private static final PointF DA = new PointF();
    private static final PointF DM = new PointF();

    private SlantUtils() {
        //no instance
    }

    static float distance(PointF one, PointF two) {
        return (float) Math.sqrt(Math.pow(two.getPointX() - one.getPointX(), 2) + Math.pow(two.getPointY() - one.getPointY(), 2));
    }

    static List<SlantArea> cutAreaWith(SlantArea area, SlantLine line) {
        List<SlantArea> areas = new ArrayList<>();
        SlantArea area1 = new SlantArea(area);
        SlantArea area2 = new SlantArea(area);

        if (line.direction == Line.Direction.HORIZONTAL) {
            area1.lineBottom = line;
            area1.leftBottom = line.start;
            area1.rightBottom = line.end;

            area2.lineTop = line;
            area2.leftTop = line.start;
            area2.rightTop = line.end;
        } else {
            area1.lineRight = line;
            area1.rightTop = line.start;
            area1.rightBottom = line.end;

            area2.lineLeft = line;
            area2.leftTop = line.start;
            area2.leftBottom = line.end;
        }

        areas.add(area1);
        areas.add(area2);

        return areas;
    }

    static SlantLine createLine(SlantArea area, Line.Direction direction, float startratio,
                                float endratio) {
        SlantLine line = new SlantLine(direction);

        if (direction == Line.Direction.HORIZONTAL) {
            line.start = getPoint(area.leftTop, area.leftBottom, Line.Direction.VERTICAL, startratio);
            line.end = getPoint(area.rightTop, area.rightBottom, Line.Direction.VERTICAL, endratio);

            line.attachLineStart = area.lineLeft;
            line.attachLineEnd = area.lineRight;

            line.upperLine = area.lineBottom;
            line.lowerLine = area.lineTop;
        } else {
            line.start = getPoint(area.leftTop, area.rightTop, Line.Direction.HORIZONTAL, startratio);
            line.end = getPoint(area.leftBottom, area.rightBottom, Line.Direction.HORIZONTAL, endratio);

            line.attachLineStart = area.lineTop;
            line.attachLineEnd = area.lineBottom;

            line.upperLine = area.lineRight;
            line.lowerLine = area.lineLeft;
        }

        return line;
    }

    static Pair<List<SlantLine>, List<SlantArea>> cutAreaWith(final SlantArea area,
                                                              final int horizontalSize, final int verticalSize) {
        List<SlantArea> areaList = new ArrayList<>();
        List<SlantLine> horizontalLines = new ArrayList<>(horizontalSize);

        SlantArea restArea = new SlantArea(area);
        for (int i = horizontalSize + 1; i > 1; i--) {
            SlantLine horizontalLine =
                createLine(restArea, Line.Direction.HORIZONTAL, (float) (i - 1) / i - 0.025f, (float) (i - 1) / i + 0.025f);
            horizontalLines.add(horizontalLine);
            restArea.lineBottom = horizontalLine;
            restArea.leftBottom = horizontalLine.start;
            restArea.rightBottom = horizontalLine.end;
        }
        List<SlantLine> verticalLines = new ArrayList<>();

        restArea = new SlantArea(area);
        for (int i = verticalSize + 1; i > 1; i--) {
            SlantLine verticalLine =
                createLine(restArea, Line.Direction.VERTICAL, (float) (i - 1) / i + 0.025f, (float) (i - 1) / i - 0.025f);
            verticalLines.add(verticalLine);
            SlantArea spiltArea = new SlantArea(restArea);
            spiltArea.lineLeft = verticalLine;
            spiltArea.leftTop = verticalLine.start;
            spiltArea.leftBottom = verticalLine.end;

            for (int j = 0; j <= horizontalLines.size(); j++) {
                SlantArea blockArea = new SlantArea(spiltArea);
                if (j == 0) {
                    blockArea.lineTop = horizontalLines.get(j);
                } else if (j == horizontalLines.size()) {
                    blockArea.lineBottom = horizontalLines.get(j - 1);

                    CrossoverPointF leftBottom =
                        new CrossoverPointF(blockArea.lineBottom, blockArea.lineLeft);
                    intersectionOfLines(leftBottom, blockArea.lineBottom, blockArea.lineLeft);
                    CrossoverPointF rightBottom =
                        new CrossoverPointF(blockArea.lineBottom, blockArea.lineRight);
                    intersectionOfLines(rightBottom, blockArea.lineBottom, blockArea.lineRight);
                    blockArea.leftBottom = leftBottom;
                    blockArea.rightBottom = rightBottom;
                } else {
                    blockArea.lineTop = horizontalLines.get(j);
                    blockArea.lineBottom = horizontalLines.get(j - 1);
                }
                CrossoverPointF leftTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineLeft);
                intersectionOfLines(leftTop, blockArea.lineTop, blockArea.lineLeft);
                CrossoverPointF rightTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineRight);
                intersectionOfLines(rightTop, blockArea.lineTop, blockArea.lineRight);
                blockArea.leftTop = leftTop;
                blockArea.rightTop = rightTop;
                areaList.add(blockArea);
            }
            restArea.lineRight = verticalLine;
            restArea.rightTop = verticalLine.start;
            restArea.rightBottom = verticalLine.end;
        }

        for (int j = 0; j <= horizontalLines.size(); j++) {
            SlantArea blockArea = new SlantArea(restArea);
            if (j == 0) {
                blockArea.lineTop = horizontalLines.get(j);
            } else if (j == horizontalLines.size()) {
                blockArea.lineBottom = horizontalLines.get(j - 1);
                CrossoverPointF leftBottom = new CrossoverPointF(blockArea.lineBottom, blockArea.lineLeft);
                intersectionOfLines(leftBottom, blockArea.lineBottom, blockArea.lineLeft);
                CrossoverPointF rightBottom =
                    new CrossoverPointF(blockArea.lineBottom, blockArea.lineRight);
                intersectionOfLines(rightBottom, blockArea.lineBottom, blockArea.lineRight);
                blockArea.leftBottom = leftBottom;
                blockArea.rightBottom = rightBottom;
            } else {
                blockArea.lineTop = horizontalLines.get(j);
                blockArea.lineBottom = horizontalLines.get(j - 1);
            }
            CrossoverPointF leftTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineLeft);
            intersectionOfLines(leftTop, blockArea.lineTop, blockArea.lineLeft);
            CrossoverPointF rightTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineRight);
            intersectionOfLines(rightTop, blockArea.lineTop, blockArea.lineRight);
            blockArea.leftTop = leftTop;
            blockArea.rightTop = rightTop;
            areaList.add(blockArea);
        }

        List<SlantLine> lines = new ArrayList<>();
        lines.addAll(horizontalLines);
        lines.addAll(verticalLines);
        return new Pair<>(lines, areaList);
    }

    static List<SlantArea> cutAreaCross(final SlantArea area, final SlantLine horizontal,
                                        final SlantLine vertical) {
        List<SlantArea> list = new ArrayList<>();

        CrossoverPointF crossoverPointF = new CrossoverPointF(horizontal, vertical);
        intersectionOfLines(crossoverPointF, horizontal, vertical);

        SlantArea one = new SlantArea(area);
        one.lineBottom = horizontal;
        one.lineRight = vertical;

        one.rightTop = vertical.start;
        one.rightBottom = crossoverPointF;
        one.leftBottom = horizontal.start;
        list.add(one);

        SlantArea two = new SlantArea(area);
        two.lineBottom = horizontal;
        two.lineLeft = vertical;

        two.leftTop = vertical.start;
        two.rightBottom = horizontal.end;
        two.leftBottom = crossoverPointF;
        list.add(two);

        SlantArea three = new SlantArea(area);
        three.lineTop = horizontal;
        three.lineRight = vertical;

        three.leftTop = horizontal.start;
        three.rightTop = crossoverPointF;
        three.rightBottom = vertical.end;
        list.add(three);

        SlantArea four = new SlantArea(area);
        four.lineTop = horizontal;
        four.lineLeft = vertical;

        four.leftTop = crossoverPointF;
        four.rightTop = horizontal.end;
        four.leftBottom = vertical.end;
        list.add(four);

        return list;
    }

    private static CrossoverPointF getPoint(final PointF start, final PointF end,
                                            final Line.Direction direction, float ratio) {
        CrossoverPointF pointF = new CrossoverPointF();
        getPoint(pointF, start, end, direction, ratio);
        return pointF;
    }

    static void getPoint(final PointF dst, final PointF start, final PointF end,
                         final Line.Direction direction, float ratio) {
        float deltaY = Math.abs(start.getPointY() - end.getPointY());
        float deltaX = Math.abs(start.getPointX() - end.getPointX());
        float maxY = Math.max(start.getPointY(), end.getPointY());
        float minY = Math.min(start.getPointY(), end.getPointY());
        float maxX = Math.max(start.getPointX(), end.getPointX());
        float minX = Math.min(start.getPointX(), end.getPointX());
        if (direction == Line.Direction.HORIZONTAL) {
            dst.setPointX(minX + deltaX * ratio);
            if (start.getPointY() < end.getPointY()) {
                dst.setPointY(minY + ratio * deltaY);
            } else {
                dst.setPointY(maxY - ratio * deltaY);
            }
        } else {
            dst.setPointY(minY + deltaY * ratio);
            if (start.getPointX() < end.getPointX()) {
                dst.setPointX(minX + ratio * deltaX);
            } else {
                dst.setPointX(maxX - ratio * deltaX);
            }
        }
    }

    // 叉乘
    private static float crossProduct(final PointF a, final PointF b) {
        return a.getPointX() * b.getPointY() - b.getPointX() * a.getPointY();
    }

    /**
     * 判断一个斜线区域是否包含(x,y)点
     *
     * @param area 斜线区域
     * @param x    x
     * @param y    y
     * @return 是否包含
     */
    static boolean contains(SlantArea area, float x, float y) {
        AB.set(area.rightTop.getPointX() - area.leftTop.getPointX(), area.rightTop.getPointY() - area.leftTop.getPointY());
        AM.set(x - area.leftTop.getPointX(), y - area.leftTop.getPointY());
        BC.set(area.rightBottom.getPointX() - area.rightTop.getPointX(), area.rightBottom.getPointY() - area.rightTop.getPointY());
        BM.set(x - area.rightTop.getPointX(), y - area.rightTop.getPointY());
        CD.set(area.leftBottom.getPointX() - area.rightBottom.getPointX(), area.leftBottom.getPointY() - area.rightBottom.getPointY());
        CM.set(x - area.rightBottom.getPointX(), y - area.rightBottom.getPointY());
        DA.set(area.leftTop.getPointX() - area.leftBottom.getPointX(), area.leftTop.getPointY() - area.leftBottom.getPointY());
        DM.set(x - area.leftBottom.getPointX(), y - area.leftBottom.getPointY());
        return crossProduct(AB, AM) > 0
            && crossProduct(BC, BM) > 0
            && crossProduct(CD, CM) > 0
            && crossProduct(DA, DM) > 0;
    }

    static boolean contains(SlantLine line, float x, float y, float extra) {
        PointF start = line.start;
        PointF end = line.end;
        if (line.direction == Line.Direction.VERTICAL) {
            A.set(start.getPointX() - extra, start.getPointY());
            B.set(start.getPointX() + extra, start.getPointY());
            C.set(end.getPointX() + extra, end.getPointY());
            D.set(end.getPointX() - extra, end.getPointY());
        } else {
            A.set(start.getPointX(), start.getPointY() - extra);
            B.set(end.getPointX(), end.getPointY() - extra);
            C.set(end.getPointX(), end.getPointY() + extra);
            D.set(start.getPointX(), start.getPointY() + extra);
        }

        AB.set(B.getPointX() - A.getPointX(), B.getPointY() - A.getPointY());
        AM.set(x - A.getPointX(), y - A.getPointY());
        BC.set(C.getPointX() - B.getPointX(), C.getPointY() - B.getPointY());
        BM.set(x - B.getPointX(), y - B.getPointY());
        CD.set(D.getPointX() - C.getPointX(), D.getPointY() - C.getPointY());
        CM.set(x - C.getPointX(), y - C.getPointY());
        DA.set(A.getPointX() - D.getPointX(), A.getPointY() - D.getPointY());
        DM.set(x - D.getPointX(), y - D.getPointY());
        return crossProduct(AB, AM) > 0
            && crossProduct(BC, BM) > 0
            && crossProduct(CD, CM) > 0
            && crossProduct(DA, DM) > 0;
    }

    /**
     * 计算两线的交点
     *
     * @param dst     计算出的交点
     * @param lineOne 线一
     * @param lineTwo 线二
     */
    static void intersectionOfLines(final CrossoverPointF dst, final SlantLine lineOne,
                                    final SlantLine lineTwo) {
        dst.horizontal = lineOne;
        dst.vertical = lineTwo;
        if (isParallel(lineOne, lineTwo)) {
            dst.set(0, 0);
            return;
        }

        if (isHorizontalLine(lineOne) && isVerticalLine(lineTwo)) {
            dst.set(lineTwo.start.getPointX(), lineOne.start.getPointY());
            return;
        }

        if (isVerticalLine(lineOne) && isHorizontalLine(lineTwo)) {
            dst.set(lineOne.start.getPointX(), lineTwo.start.getPointY());
            return;
        }

        if (isHorizontalLine(lineOne) && !isVerticalLine(lineTwo)) {
            float k = calculateSlope(lineTwo);
            float b = calculateVerticalIntercept(lineTwo);
            dst.setPointY(lineOne.start.getPointY());
            dst.setPointX((dst.getPointY() - b) / k);
            return;
        }

        if (isVerticalLine(lineOne) && !isHorizontalLine(lineTwo)) {
            float k = calculateSlope(lineTwo);
            float b = calculateVerticalIntercept(lineTwo);
            dst.setPointX(lineOne.start.getPointX());
            dst.setPointY( k * dst.getPointX() + b);
            return;
        }

        if (isHorizontalLine(lineTwo) && !isVerticalLine(lineOne)) {
            float k = calculateSlope(lineOne);
            float b = calculateVerticalIntercept(lineOne);
            dst.setPointY(lineTwo.start.getPointY());
            dst.setPointX((dst.getPointY() - b) / k);
            return;
        }

        if (isVerticalLine(lineTwo) && !isHorizontalLine(lineOne)) {
            float k = calculateSlope(lineOne);
            float b = calculateVerticalIntercept(lineOne);
            dst.setPointX(lineTwo.start.getPointX());
            dst.setPointY(k * dst.getPointX() + b);
            return;
        }

        final float k1 = calculateSlope(lineOne);
        final float b1 = calculateVerticalIntercept(lineOne);

        final float k2 = calculateSlope(lineTwo);
        final float b2 = calculateVerticalIntercept(lineTwo);
        dst.setPointX((b2 - b1) / (k1 - k2));
        dst.setPointY(dst.getPointX() * k1 + b1);
    }

    private static boolean isHorizontalLine(SlantLine line) {
        return line.start.getPointY() == line.end.getPointY();
    }

    private static boolean isVerticalLine(SlantLine line) {
        return line.start.getPointX() == line.end.getPointX();
    }

    /**
     * 判断两条线是否平行
     *
     * @param lineOne 第一条
     * @param lineTwo 第二条
     * @return 是否平行
     */
    private static boolean isParallel(final SlantLine lineOne, final SlantLine lineTwo) {
        return calculateSlope(lineOne) == calculateSlope(lineTwo);
    }

    /**
     * 计算线的斜率
     *
     * @param line 线
     * @return 线的斜率
     */
    static float calculateSlope(final SlantLine line) {
        if (isHorizontalLine(line)) {
            return 0f;
        } else if (isVerticalLine(line)) {
            return Float.POSITIVE_INFINITY;
        } else {
            return (line.start.getPointY() - line.end.getPointY()) / (line.start.getPointX() - line.end.getPointX());
        }
    }

    /**
     * 计算纵截距
     *
     * @param line 线
     * @return 纵截距
     */
    private static float calculateVerticalIntercept(final SlantLine line) {
        if (isHorizontalLine(line)) {
            return line.start.getPointY();
        } else if (isVerticalLine(line)) {
            return Float.POSITIVE_INFINITY;
        } else {
            float k = calculateSlope(line);
            return line.start.getPointY() - k * line.start.getPointX();
        }
    }
}
